/*
 * Copyright 2009 Original Author(s)
 * 
 * This file is part of Kommando
 * 
 * Kommando is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Kommando is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Kommando.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.kommando.core.selector;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.kommando.core.Displayable;
import org.kommando.core.search.Result;
import org.kommando.core.util.Assert;
import org.kommando.ui.ResultListCellRenderer;
import org.kommando.ui.support.PrototypeResult;
import org.kommando.ui.util.TriangleSquareWindowsCornerIcon;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventListModel;

/**
 * The {@link SelectionList} is responsible for displaying a list of possible results to the user.
 * 
 * @author Peter De Bruycker
 */
public class SelectionList<T extends Displayable> {
    private EventList<Result<T>> results = new BasicEventList<Result<T>>();
    private JList selectionList;

    private JDialog selectionListWindow;
    private JLabel queryLabel;
    private JLabel countLabel;

    private boolean updatingState;

    public SelectionList(final AbstractSelector<T> selector) {
        selectionListWindow = new JDialog();
        selectionListWindow.setUndecorated(true);

        JPanel contentPanel = new JPanel(new BorderLayout());

        queryLabel = new JLabel(" ");
        contentPanel.add(queryLabel, BorderLayout.NORTH);

        selectionList = new JList(new EventListModel<Result<T>>(results));

        // popup menu
        selectionList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    showPopup(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    showPopup(e);
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    showPopup(e);
                }
            }

            private void showPopup(MouseEvent e) {
                Result<T> selection = (Result<T>) selectionList.getSelectedValue();
                if (selection != null) {
                    JPopupMenu popupMenu = selector.createPopupMenu(selection.getValue());
                    if (popupMenu != null) {
                        popupMenu.show(e.getComponent(), e.getX(), e.getY());
                    }
                }

            }
        });

        selectionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        selectionList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting()) {
                    return;
                }

                if (updatingState) {
                    return;
                }

                selector.setSelection((Result<T>) selectionList.getSelectedValue());
            }
        });

        JScrollPane scrollPane = new JScrollPane(selectionList);
        scrollPane.setBorder(null);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        contentPanel.add(scrollPane);

        selectionList.setCellRenderer(new ResultListCellRenderer(new Dimension(32, 32)));
        selectionList.setVisibleRowCount(10);
        selectionList.setPrototypeCellValue(new PrototypeResult());

        selectionListWindow.add(contentPanel);
        selectionListWindow.pack();

        selectionListWindow.addWindowListener(new WindowAdapter() {
            @Override
            public void windowDeactivated(WindowEvent e) {
                e.getWindow().setVisible(false);
            }

            @Override
            public void windowLostFocus(WindowEvent e) {
                e.getWindow().setVisible(false);
            }
        });

        JPanel statusPanel = new JPanel(new BorderLayout());

        countLabel = new JLabel(" ");

        statusPanel.add(countLabel);

        // TODO improve the resizer: respect minimum size, smoother dragging, snap to row
        JLabel resizer = new JLabel(new TriangleSquareWindowsCornerIcon());
        resizer.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                int listHeight = selectionList.getHeight();
                int rowHeight = listHeight / selectionList.getVisibleRowCount();

                Point windowLocation = selectionListWindow.getLocationOnScreen();
                Point mouseLocation = e.getLocationOnScreen();

                final int width = Math.abs(mouseLocation.x - windowLocation.x);
                final int height = Math.abs(mouseLocation.y - windowLocation.y);

                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        selectionListWindow.setSize(width, height);
                    }
                });
            }
        });
        resizer.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
        statusPanel.add(resizer, BorderLayout.LINE_END);
        contentPanel.add(statusPanel, BorderLayout.SOUTH);
    }

    public void show(Point location) {
        Assert.argumentNotNull("location", location);

        selectionListWindow.setLocation(location);
        selectionListWindow.setVisible(true);
        selectionListWindow.toFront();
        selectionListWindow.requestFocusInWindow();
        selectionList.requestFocusInWindow();

        if (results.size() > 0) {
            selectionList.setSelectedValue(results.get(0), true);
        }
    }

    public void hide() {
        if (selectionListWindow != null) {
            selectionListWindow.setVisible(false);
        }
    }

    private void setQuery(String query) {
        queryLabel.setText(query.length() == 0 ? " " : query);
    }

    private void setResults(List<Result<T>> results) {
        Assert.argumentNotNull("results", results);

        this.results.clear();
        this.results.addAll(results);

        countLabel.setText(String.format("%d of %d items", 1, results.size()));
    }

    public boolean isVisible() {
        return selectionListWindow.isVisible();
    }

    public void setState(SelectorState<T> state) {
        if (updatingState) {
            return;
        }

        try {
            updatingState = true;

            setQuery(state.getQuery());
            setResults(state.getResults());
            selectionList.setSelectedValue(state.getSelection(), true);
        } finally {
            updatingState = false;
        }
    }
}
